﻿using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using Microsoft.Win32;

namespace Milan.Net
{
	/// <summary>
	/// Handles uploading of files in various configurations.
	/// </summary>
	public static class Upload
	{

		/// <summary>
		/// Posts a crash dump to the server.
		/// </summary>
		/// <returns></returns>
		public static string PostCrashDump(int processId, Uri uri)
		{
			Process process = Process.GetProcessById(processId);
			string fileName = process.ProcessName+"_"; // name+linkertime+.dmp
			// Build unique crash file name
			string localPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), Settings.CompanyName, "minidump", fileName);
			Milan.Windows.dbghelp.MiniDumpWriteDump(localPath);
			return PostFile(uri,fileName);
		}

		/// <summary>
		/// Uploads a file to a multi-part form, with optional post field data, cookies and header data.
		/// This is useful, for say, posting to an HTML form with a file element.  Currently accepts only one file.
		/// </summary>
		/// <param name="requestUri">The URI to use to post the data</param>
		/// <param name="postData">A NameValueCollection containing form fields to post along with file data</param>
		/// <param name="fileData">An open, positioned stream containing the file or binary data</param>
		/// <param name="fileName">Optionally, a name to assign to the uploaded file.</param>
		/// <param name="fileFieldName">Optional, a form field to give to the uploaded file.</param>
		/// <param name="fileContentType">MIME Content type, which if null or empty, attempts to determine from the registry.</param>
		/// <param name="cookies">Optional, (can be null). Used to send and retrieve cookies for maintaining state between calls.</param>
		/// <param name="headers">Optional, headers to be added to request.</param>
		/// <returns>WebResponse object, which can be used to read the request</returns>
		public static WebResponse PostFile(Uri requestUri, NameValueCollection postData, Stream fileData, string fileName = null, string fileFieldName = null,
										   string fileContentType = null, CookieContainer cookies = null, NameValueCollection headers = null)
		{
			// First, let's start the request.
			HttpWebRequest webrequest = (HttpWebRequest)WebRequest.Create(requestUri);

			// Set the method to post.
			webrequest.Method = "POST";

			// Add cookies, if we have them.
			if (cookies != null)
				webrequest.CookieContainer = cookies;

			// If we are passing optional headers, then stick them unto the request
			if (headers != null)
			{
				// set the headers
				foreach (string key in headers.AllKeys)
				{
					string[] values = headers.GetValues(key);
					if (values != null)
					{
						foreach (string value in values)
							webrequest.Headers.Add(key, value);
					}
				}
			}

			// We try to get the content type and when we can't, we call the thing an "application/octet-stream"
			string ctype;
			fileContentType = string.IsNullOrEmpty(fileContentType) ? TryGetContentType(fileName, out ctype) ? ctype : "application/octet-stream" : fileContentType;

			// The name of the field, and when null, we simply call it "file"
			fileFieldName = string.IsNullOrEmpty(fileFieldName) ? "file" : fileFieldName;

			// Start the boundary using a tick count to make sure it is unique enough.
			string boundary = "----------" + DateTime.Now.Ticks.ToString("x", CultureInfo.InvariantCulture);

			// Set the content type to multipart/form-data
			webrequest.ContentType = "multipart/form-data; boundary=" + boundary;

			StringBuilder sbHeader = new StringBuilder();
			// If we have optional form fields, then add these.
			if (postData != null)
			{
				foreach (string key in postData.AllKeys)
				{
					string[] values = postData.GetValues(key);
					if (values != null)
					{
						foreach (string value in values)
						{
							sbHeader.AppendFormat("--{0}\r\n", boundary);
							sbHeader.AppendFormat("Content-Disposition: form-data; name=\"{0}\";\r\n\r\n{1}\r\n", key, value);
						}
					}
				}
			}

			// Now, specify the header info
			if (fileData != null)
			{
				sbHeader.AppendFormat("--{0}\r\n", boundary);
				sbHeader.AppendFormat("Content-Disposition: form-data; name=\"{0}\"; {1}\r\n", fileFieldName,
									  string.IsNullOrEmpty(fileName) ? "" : string.Format(CultureInfo.InvariantCulture, "filename=\"{0}\";", Path.GetFileName(fileName)));
				sbHeader.AppendFormat("Content-Type: {0}\r\n\r\n", fileContentType);
			}

			// Set the header and footer of the data
			byte[] header = Encoding.UTF8.GetBytes(sbHeader.ToString());
			byte[] footer = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");

			// Make sure to set the content length as well.
			long contentLength = header.Length + (fileData != null ? fileData.Length : 0) + footer.Length;
			webrequest.ContentLength = contentLength;

			// Write out the data to the webrequest stream.
			using (Stream requestStream = webrequest.GetRequestStream())
			{
				requestStream.Write(header, 0, header.Length);

				if (fileData != null)
				{
					// write the file data, if any
					byte[] buffer = new Byte[checked((uint)Math.Min(4096, (int)fileData.Length))];
					int bytesRead;
					while ((bytesRead = fileData.Read(buffer, 0, buffer.Length)) != 0)
					{
						requestStream.Write(buffer, 0, bytesRead);
					}
				}

				// write footer
				requestStream.Write(footer, 0, footer.Length);

				// Return a web response object
				return webrequest.GetResponse();
			}
		}

		/// <summary>
		/// Uploads a file using a multi-part/form-data POST.
		/// </summary>
		/// <param name="requestUri">The URI to use to post the data</param>
		/// <param name="postData">A NameValueCollection containing form fields to post along with file data</param>
		/// <param name="filePath">The path of the file to upload</param>
		/// <param name="fileFieldName">Optional, a form field name to assign to the uploaded file data, or "file" will be used if null or empty.</param>
		/// <param name="fileContentType">MIME Content type, which if null or empty, attempts to determine from the registry.</param>
		/// <param name="cookies">Optional, can pass null. Used to send and retrieve cookies. 
		/// Pass the same instance to subsequent calls to maintain state if required.</param>
		/// <param name="headers">Optional, headers to be added to request.</param>
		/// <returns></returns>
		public static WebResponse PostFile(Uri requestUri, NameValueCollection postData, string filePath, string fileFieldName = null,
										   string fileContentType = null, CookieContainer cookies = null, NameValueCollection headers = null)
		{
			using (FileStream fileData = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
			{
				return PostFile(requestUri, postData, fileData, System.IO.Path.GetFileName(filePath), fileFieldName, fileContentType, cookies, headers);
			}
		}

		/// <summary>
		/// Simply post a file and some fields to a form, returning the response.
		/// </summary>
		/// <param name="uri">Where to post</param>
		/// <param name="filePath">Path to file to upload</param>
		/// <param name="postData">NameValueCollection with form name, value pairs</param>
		/// <returns>A null on error or the response from the server.  Can also throw an exception on connectivity error.</returns>
		public static string PostFile(Uri uri, string filePath, NameValueCollection postData = null)
		{
			string responseText = null;
			using (WebResponse response = Upload.PostFile(uri, postData, filePath, null, null, null, null))
			{
				// the stream returned by WebResponse.GetResponseStream will contain any content returned by the server after upload
				using (StreamReader reader = new StreamReader(response.GetResponseStream()))
				{
					responseText = reader.ReadToEnd();
	            }
			}
			return responseText;
		}

		/// <summary>
		/// Posts a file encrypted.
		/// </summary>
		/// <param name="uri"></param>
		/// <param name="filePath"></param>
		/// <param name="postData"></param>
		/// <returns></returns>
		public static string PostFileCompressed(Uri uri, string filePath, NameValueCollection postData = null)
		{
			string responseText = null;
			MemoryStream stream = new MemoryStream(Milan.ZLib.LoadFileAndCompress(filePath));
			using (WebResponse response = Upload.PostFile(uri, postData, stream, postData["dest"], null, null, null))
			{
				// the stream returned by WebResponse.GetResponseStream will contain any content returned by the server after upload
				using (StreamReader reader = new StreamReader(response.GetResponseStream()))
				{
					responseText = reader.ReadToEnd();
				}
			}
			return responseText;
		}

		/// <summary>
		/// Simply post a file and some fields to a form, returning the response.
		/// </summary>
		/// <param name="uri">Where to post</param>
		/// <param name="text">The</param>
		/// <param name="postData">NameValueCollection with form name, value pairs</param>
		/// <returns>A null on error or the response from the server.  Can also throw an exception on connectivity error.</returns>
		public static string PostTextAsFile(Uri uri, string text, string fileName, NameValueCollection postData = null)
		{
			string responseText = null;
			Stream stringStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(text));
			using (WebResponse response = Upload.PostFile(uri, postData, stringStream, fileName, null, null, null))
			{
				// the stream returned by WebResponse.GetResponseStream will contain any content returned by the server after upload
				using (StreamReader reader = new StreamReader(response.GetResponseStream()))
				{
					responseText = reader.ReadToEnd();
				}
			}
			return responseText;
		}

		/// <summary>
		/// Post post vars only with no file.
		/// </summary>
		/// <param name="uri"></param>
		/// <param name="postData"></param>
		/// <returns></returns>
		public static string PostVarsOnly(Uri uri, NameValueCollection postData = null)
		{
			string responseText = null;
			using (WebResponse response = PostFile(uri, postData, null, null, null, null, null, null))
			{
				using (StreamReader reader = new StreamReader(response.GetResponseStream()))
				{
					responseText = reader.ReadToEnd();
				}
			}
			return responseText;
		}

		/// <summary>
		/// Tries to query registry for content-type of supplied file name.
		/// </summary>
		/// <param name="fileName"></param>
		/// <param name="contentType"></param>
		/// <returns></returns>
		public static bool TryGetContentType(string fileName, out string contentType)
		{
			try
			{
				RegistryKey key = Registry.ClassesRoot.OpenSubKey(@"MIME\Database\Content Type");

				if (key != null)
				{
					foreach (string keyName in key.GetSubKeyNames())
					{
						RegistryKey subKey = key.OpenSubKey(keyName);
						if (subKey != null)
						{
							string subKeyValue = (string)subKey.GetValue("Extension");

							if (!string.IsNullOrEmpty(subKeyValue))
							{
								if (string.Compare(Path.GetExtension(fileName).ToUpperInvariant(), subKeyValue.ToUpperInvariant(), StringComparison.OrdinalIgnoreCase) == 0)
								{
									contentType = keyName;
									return true;
								}
							}
						}
					}
				}
			}
			catch
			{
				// In a Try routine, we simply return false if anything goes wrong
			}
			contentType = "";
			return false;
		}

	}
}